#include "PWM.h"
#include "cJSON.h"
#include "delay.h"
#include "do_dns.h"
#include "do_mqtt.h"
#include "light.h"
#include "w55mh32_gpio.h"
#include "wiz_interface.h"
#include "wizchip_conf.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

// Macro definitions
#define MQTT_ETHERNET_MAX_SIZE                                                 \
  (1024 * 2) // Maximum length of MQTT send/receive buffer (bytes)
#define PUBLISH_INTERVAL 3000 // Data publishing interval (milliseconds)

// External variable declarations
extern uint16_t g_light_intensity; // Light sensor data
extern bool g_is_person_detected;  // Infrared sensor data
extern uint16_t g_pwm_duty_cycle;  // PWM duty cycle setting
extern uint16_t g_mode_switch;     // Mode selection

/**
 * Global variables
 */
uint16_t g_brightness_set =
    20; // LED brightness value issued by web page (0-100)
uint16_t g_light_thres_low =
    150; // Light threshold lower limit (for automatic mode)
uint16_t g_low_bright =
    20;                  // LED low brightness setting in automatic mode (0-100)
uint32_t tick_count = 0; // System tick counter

// MQTT core objects
MQTTClient c = {0}; // MQTT client instance
Network n = {0};    // Network instance
int conn_ok;        // Connection status flag

/**
 * Current running status
 */
static MQTT_Status s_run_status = MQTT_CONN;

/**
 * MQTT connection parameter configuration
 */
mqttconn mqtt_params = {
    .mqtt_host_url = "mqtts.heclouds.com",
    .server_ip = {0},
    .port = 1883,
    .clientid = "W55MH32",
    .username = "tt1I6f0jwn",
    .passwd =
        "version=2018-10-31&res=products%2Ftt1I6f0jwn%2Fdevices%2FW55MH32&et="
        "1815642960&method=md5&sign=18yKGog9SKuqimWqsfBUsg%3D%3D",
    .pubtopic = "$sys/tt1I6f0jwn/W55MH32/thing/property/post",
    .pubtopic_reply = "$sys/tt1I6f0jwn/W55MH32/thing/property/post/reply",
    .subtopic = "$sys/tt1I6f0jwn/W55MH32/thing/property/set",
    .subtopic_reply = "$sys/tt1I6f0jwn/W55MH32/thing/property/set_reply",
    .pub_qos = QOS0,
    .willtopic = "/wizchip/will",
    .will_qos = QOS0,
    .willmsg = "wizchip offline!",
    .sub_qos = QOS0,
};

MQTTMessage pubmessage = // MQTT publish message structure
    {
        .qos = QOS0,
        .retained = 0,
        .dup = 0,
        .id = 0,
};

/**
 * Receive buffer and flags
 */
static char s_mqtt_recv_msg[512] = {0}; // Receive message buffer
static uint8_t s_mqtt_recv_flag = 0;    // Receive flag (1=new message)

// MQTT connection configuration object
MQTTPacket_willOptions willdata = MQTTPacket_willOptions_initializer;
MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
unsigned char *data_ptr = NULL;

/**
 * Publish timing variables
 */
static uint32_t s_last_publish_time =
    0; // Last publish time (for timed publishing)

/**
 * @brief Initialize MQTT client
 * @param sn: socket number
 * @param send_buf: send buffer
 * @param recv_buf: receive buffer
 * @note DNS resolution failure will enter an infinite loop to ensure successful
 * network initialization
 */
void mqtt_init(uint8_t sn, uint8_t *send_buf, uint8_t *recv_buf) {
  wiz_NetInfo get_info = {0};
  wizchip_getnetinfo(&get_info);

  /* DNS resolution: failure enters infinite loop */
  if (do_dns(send_buf, (uint8_t *)mqtt_params.mqtt_host_url,
             mqtt_params.server_ip)) {
    while (1)
      ;
  }

  NewNetwork(&n, sn);
  ConnectNetwork(&n, mqtt_params.server_ip, mqtt_params.port);
  MQTTClientInit(&c, &n, 1000, send_buf, MQTT_ETHERNET_MAX_SIZE, recv_buf,
                 MQTT_ETHERNET_MAX_SIZE);

  // Initialize MQTT connection parameters
  data.willFlag = 0;
  willdata.qos = mqtt_params.will_qos;
  willdata.topicName.lenstring.data = mqtt_params.willtopic;
  willdata.topicName.lenstring.len = strlen(willdata.topicName.lenstring.data);
  willdata.message.lenstring.data = mqtt_params.willmsg;
  willdata.message.lenstring.len = strlen(willdata.message.lenstring.data);
  willdata.retained = 0;
  willdata.struct_version = 3;
  data.will = willdata;
  data.MQTTVersion = 4;
  data.clientID.cstring = mqtt_params.clientid;
  data.username.cstring = mqtt_params.username;
  data.password.cstring = mqtt_params.passwd;
  data.keepAliveInterval = 30;
  data.cleansession = 1;
}

/**
 * @brief Parse JSON messages sent from cloud platform
 * @param msg: JSON string to be parsed
 * @note Extract LED control, mode switching and other parameters, update device
 * status and reply to platform
 */
void json_decode(char *msg) {
  int ret;
  char replymsg[128] = {0};
  cJSON *id = NULL;
  cJSON *jsondata = NULL;
  cJSON *params = NULL;
  cJSON *led = NULL;
  cJSON *mode_switch = NULL;
  cJSON *brightness_set = NULL;
  cJSON *light_threshold = NULL;

  jsondata = cJSON_Parse(msg);
  if (jsondata == NULL) {
    printf("json parse fail.\r\n");
    return;
  }

  id = cJSON_GetObjectItem(jsondata, "id");
  params = cJSON_GetObjectItem(jsondata, "params");
  led = cJSON_GetObjectItem(params, "LED");
  mode_switch = cJSON_GetObjectItem(params, "Mode_switch");
  brightness_set = cJSON_GetObjectItem(params, "Brightness_set");
  light_threshold = cJSON_GetObjectItem(params, "light_threshold");

  // Process LED control command
  if (led != NULL) {
    g_pwm_duty_cycle = led->valueint ? 20 : 0;
  }

  // Process mode switching command
  if (mode_switch != NULL) {
    g_mode_switch = mode_switch->valueint;
    if (!g_mode_switch) {
      g_pwm_duty_cycle = 0; // Manual mode turns off LED
    }
  }

  // Process brightness setting command
  if (brightness_set != NULL && brightness_set->valueint) {
    g_brightness_set = brightness_set->valueint;
    g_pwm_duty_cycle = g_brightness_set;
    g_low_bright = g_brightness_set;
  }

  // Process light threshold command
  if (light_threshold != NULL && light_threshold->valueint) {
    g_light_thres_low = light_threshold->valueint;
  }

  pwm_set_compare1(g_pwm_duty_cycle); // Apply PWM settings

  // Reply to cloud platform
  pubmessage.qos = QOS0;
  sprintf(replymsg, "{\"id\":\"%s\",\"code\":200,\"msg\":\"success\"}",
          id->valuestring);
  printf("reply:%s\r\n", replymsg);
  pubmessage.payload = replymsg;
  pubmessage.payloadlen = strlen(replymsg);

  ret = MQTTPublish(&c, mqtt_params.subtopic_reply, &pubmessage);
  if (ret != SUCCESSS) {
    s_run_status = MQTT_ERR;
  } else {
    printf("publish:%s,%s\r\n\r\n", mqtt_params.subtopic_reply,
           (char *)pubmessage.payload);
  }

  cJSON_Delete(jsondata); // Release JSON resources
}

/**
 * @brief MQTT message receive callback function
 * @param md: message data structure (contains topic and content)
 * @note Save messages from subscribed topics to buffer and set flag
 */
void message_arrived(MessageData *md) {
  char topicname[64] = {0};
  char msg[512] = {0};

  // Extract topic and message content
  sprintf(topicname, "%.*s", (int)md->topicName->lenstring.len,
          md->topicName->lenstring.data);
  sprintf(msg, "%.*s", (int)md->message->payloadlen,
          (char *)md->message->payload);
  printf("recv:%s,%s\r\n\r\n", topicname, msg);

  // Save message when matching subscribed topic
  if (strcmp(topicname, mqtt_params.subtopic) == 0) {
    s_mqtt_recv_flag = 1;
    memset(s_mqtt_recv_msg, 0, sizeof(s_mqtt_recv_msg));
    memcpy(s_mqtt_recv_msg, msg, strlen(msg));
  }
}

/**
 * @brief Generate JSON string of sensor data
 * @param json_buf: output buffer
 * @param buf_len: buffer length
 * @return 1-success, 0-failure
 */
static int generate_sensor_json(char *json_buf, uint16_t buf_len) {
  if (json_buf == NULL || buf_len < 256) {
    return 0;
  }

  sprintf(json_buf,
          "{\"id\":\"123\",\"version\":\"1.0\",\"params\":{"
          "\"light\":{\"value\":%d},"
          "\"induct\":{\"value\":%s},"
          "\"LED\":{\"value\":%s},"
          "\"Mode_switch\":{\"value\":%s},"
          "\"light_threshold\":{\"value\":%d},"
          "\"Brightness_set\":{\"value\":%d}"
          "}}",
          g_light_intensity, g_is_person_detected ? "true" : "false",
          g_pwm_duty_cycle ? "true" : "false", g_mode_switch ? "true" : "false",
          g_light_thres_low, g_brightness_set);

  return 1;
}

/**
 * @brief Execute MQTT state machine logic
 * @note Handle connection, subscription, publishing, heartbeat maintenance and
 * error reconnection by status
 */
void do_mqtt(void) {
  uint8_t ret;
  static char json_payload[256] = {0};
  uint32_t current_time = get_tick_count();

  switch (s_run_status) {
  case MQTT_CONN: {
    ret = MQTTConnect(&c, &data);
    printf("Connect to MQTT server: %d.%d.%d.%d:%d\r\n",
           mqtt_params.server_ip[0], mqtt_params.server_ip[1],
           mqtt_params.server_ip[2], mqtt_params.server_ip[3],
           mqtt_params.port);
    printf("Connected: %s\r\n\r\n", ret == SUCCESSS ? "success" : "failed");

    s_run_status = (ret == SUCCESSS) ? MQTT_SUB : MQTT_ERR;
    break;
  }

  case MQTT_SUB: {
    // Subscribe to property setting topic
    ret = MQTTSubscribe(&c, mqtt_params.subtopic, mqtt_params.sub_qos,
                        message_arrived);
    printf("Subscribing to %s: %s\r\n", mqtt_params.subtopic,
           ret == SUCCESSS ? "success" : "failed");
    if (ret != SUCCESSS) {
      s_run_status = MQTT_ERR;
      break;
    }

    // Subscribe to publish reply topic
    ret = MQTTSubscribe(&c, mqtt_params.pubtopic_reply, mqtt_params.sub_qos,
                        message_arrived);
    printf("Subscribing to %s: %s\r\n\r\n", mqtt_params.pubtopic_reply,
           ret == SUCCESSS ? "success" : "failed");
    s_run_status = (ret == SUCCESSS) ? MQTT_PUB_MESSAGE : MQTT_ERR;
    if (s_run_status == MQTT_PUB_MESSAGE) {
      s_last_publish_time = get_tick_count();
    }
    break;
  }

  case MQTT_PUB_MESSAGE: {
    if (!generate_sensor_json(json_payload, sizeof(json_payload))) {
      printf("JSON generate failed!\r\n");
      s_run_status = MQTT_ERR;
      break;
    }

    pubmessage.qos = QOS0;
    pubmessage.payload = json_payload;
    pubmessage.payloadlen = strlen(json_payload);

    ret = MQTTPublish(&c, mqtt_params.pubtopic, &pubmessage);
    if (ret != SUCCESSS) {
      printf("Publish failed!\r\n");
      s_run_status = MQTT_ERR;
    } else {
      printf("Publish to %s: %s\r\n\r\n", mqtt_params.pubtopic, json_payload);
      s_run_status = MQTT_KEEPALIVE;
      s_last_publish_time = get_tick_count();
    }
    break;
  }

  case MQTT_KEEPALIVE: {
    // Process received messages
    if (s_mqtt_recv_flag) {
      s_mqtt_recv_flag = 0;
      json_decode(s_mqtt_recv_msg);
    }

    // Maintain heartbeat
    if (MQTTYield(&c, 100) != SUCCESSS) {
      printf("Keepalive failed!\r\n");
      s_run_status = MQTT_ERR;
      break;
    }

    // Timed publishing
    if (get_tick_count() - s_last_publish_time >= PUBLISH_INTERVAL) {
      s_run_status = MQTT_PUB_MESSAGE;
    }

    delay_ms(50);
    break;
  }

  case MQTT_RECV: {
    break;
  }

  case MQTT_ERR: {
    printf("System ERROR! Reconnecting...\r\n");
    delay_ms(1000);
    s_run_status = MQTT_CONN;
    break;
  }

  default:
    s_run_status = MQTT_CONN;
    break;
  }
}

/**
 * @brief Update system tick count
 */
void mqtt_tick(void) { tick_count++; }

/**
 * @brief Get current system tick count
 * @return Current tick count value
 */
uint32_t get_tick_count(void) { return tick_count; }
